---
seotitle: Defining type-safe TypeScript APIs with Encore.ts
seodesc: Learn how to create APIs for your cloud backend application using TypeScript and Encore.ts
title: Defining Type-Safe APIs
subtitle: Simplifying type-safe API development
lang: ts
---

Encore.ts simplifies creating type-safe, idiomatic TypeScript API endpoints and provides built-in validation for incoming requests.

At their core, APIs in Encore.ts are normal `async` functions with request and response data types defined as TypeScript interfaces, which Encore.ts uses to encode API requests to HTTP messages.

Encore.ts also parses your source code to understand the request and response schema of each endpoint, automatically handling validation of incoming requests against your schema.

## Defining API endpoints

To define an API endpoint, use the `api` function from the `encore.dev/api` module. This function wraps a regular TypeScript async function, designating it as an API endpoint. Encore.ts then generates the necessary boilerplate at compile-time.

In the example below, we define the API endpoint `ping` which accepts `POST` requests and is exposed as `hello.ping` (because our service name is `hello`).

```typescript
// inside the hello.ts file
import { api } from "encore.dev/api";

export const ping = api(
  { method: "POST" },
  async (p: PingParams): Promise<PingResponse> => {
    return { message: `Hello ${p.name}!` };
  },
);
```

### Exposing API endpoints to the outside world

When you define an API, by default it is not exposed to the outside world, and it can only be called
by other APIs within the same Encore application.

To expose an API to the internet, add the `expose: true` field to the options object passed in
as the first argument to `api`.

- `{ expose: false }` &ndash; defines a private API that is never accessible to the outside world. It can only be called from other services in your app and via cron jobs. This is default value if the `expose` field isn't set.
- `{ expose: true }` &ndash; defines a public API that anybody on the internet can call

### Requiring authentication data

To require authentication for an API endpoint, add `auth: true` to the API options.
With this option, Encore will first call the authentication handler you've defined to validate the authentication of incoming requests.

Setting `auth: true` can also be useful for internal APIs that aren't exposed to the internet.
In that case, it means that the internal caller must have valid authentication data associated with its request.

Finally, even if an API endpoint does not specify `auth: true`, it will still receive any authentication data that was provided.

For more information on defining APIs that require authentication, see the [authentication guide](/docs/ts/develop/auth).

## API Schemas

### Request and response schemas

In the example above we defined an API that uses request and response schemas, where the request data is of type `Params` and the response data of type `Response`.
That means we need to define them like so:

```typescript
-- hello.ts --
import { api } from "encore.dev/api";

// PingParams is the request data for the Ping endpoint.
interface PingParams {
  name: string;
}

// PingResponse is the response data for the Ping endpoint.
interface PingResponse {
  message: string;
}

// hello is an API endpoint that responds with a simple response.
export const hello = api(
  { method: "POST", path: "/hello" },
  async (p: PingParams): Promise<PingResponse> => {
    return { message: `Hello ${p.name}!` };
  },
);
```

Request and response schemas are both optional. There are four different ways of defining an API:

**Using both request and response data:**<br/>
`api({ ... }, async (params: Params): Promise<Response> => {});`

**Only returning a response:**<br/>
`api({ ... }, async (): Promise<Response> => {});`

**With only request data:**<br/>
`api({ ... }, async (params: Params): Promise<void> => {});`

**Without any request or response data:**<br/>
`api({ ... }, async (): Promise<void> => {});`

Alternatively, you can express these using type parameters, since `api` is a generic function:

**Using both request and response data:**<br/>
`api<Params, Response>({ ... }, async (params) => {});`

**Only returning a response:**<br/>
`api<void, Response>({ ... }, async () => {});`

**With only request data:**<br/>
`api<Params, void>({ ... }, async (params) => {});`

**Without any request or response data:**<br/>
`api<void, void>({ ... }, async () => {});`

### Customizing request and response encoding
Encore parses the source code to understand the request and response schema of each endpoint.
By default, the data is parsed as a JSON body for incoming requests, and written back as JSON responses.

This can be customized on a per-field basis, allowing individual fields to be parsed from query strings
and HTTP headers with ease.

This is done by using the `Header` and `Query` types defined in the `encore.dev/api` module.

### Headers

Headers are defined by setting the field type to `Header<"Name-Of-Header">`. It can be used in both request and response data types.

In the example below, the `language` field will be fetched from the
`Accept-Language` HTTP header.

```ts
import { Header } from "encore.dev/api";

interface Params {
  language: Header<"Accept-Language">; // parsed from header
  author: string; // not a header
}
```

### Query parameters

For `GET`, `HEAD` and `DELETE` requests, parameters are read from the query string by default, since those HTTP methods
do not support request bodies.

For other HTTP methods (that do support request bodies), parameters are by default read from the HTTP request body as JSON.
In those cases, the `Query` type can be used to specify that a field should be parsed from the query string instead.

Query strings are not supported in HTTP responses, and are treated as being part of the HTTP response body in JSON.

In the example below, the `limit` field will be read from the `limit` query parameter for all HTTP methods,
whereas the `author` field will be parsed from the query string only if the method of
the request is `GET`, `HEAD` or `DELETE` (and otherwise from the HTTP request body as JSON).

```ts
interface Params {
  limit: Query<number>; // always a query parameter
  author: string; // query if GET, HEAD or DELETE, otherwise body parameter
}
```

### Path parameters

Path parameters are specified by the `path` field in the API Options in `api` call.
To specify a placeholder variable, use `:name` and add a function parameter with the same name to the function signature.
Encore parses the incoming request URL and makes sure it matches the type of the parameter. The last segment of the path
can be parsed as a wildcard parameter by using `*name` with a matching function parameter.

Each path parameter (whether a single segment like `:name` or a wildcard parameter like `*name`) must have
a matching field in the request data type.

For example:

```ts
// Retrieves a blog post by its id.
export const getBlogPost = api(
    { method: "GET", path: "/blog/:id/*path" },
    async (params: {id: number; path: string}): Promise<BlogPost> {
        // Use id and path to query database...
    }
)
```

### Cookie parameters

Cookies are defined by setting the field type to `Cookie<"Name-Of-Cookie">`. It can be used in both request and response data types.

In the example below we define an optional field that will be parsed from the cookie header.

```ts
import { Cookie } from "encore.dev/api";

interface Params {
  settings?: Cookie<"settings">; // parsed from cookie header
}
```

Read more about cookies [here](/docs/ts/primitives/cookies)

### Fallback routes

Encore supports defining fallback routes that will be called if no other endpoint matches the request,
using the syntax `path: "/!fallback"`.

This is often useful when migrating an existing backend service over to Encore, as it allows you to gradually
migrate endpoints over to Encore while routing the remaining endpoints to the existing HTTP router using
a raw endpoint with a fallback route.

For example:

```ts
// Route all requests to the existing HTTP router if no other endpoint matches.
export const fallback = api.raw(
    { expose: true, method: "*", path: "/!path" },
    async (req, resp) {
        // Call old router
    }
)
```

## Custom HTTP status codes

By default, Encore automatically sets appropriate HTTP status codes for your API responses. We recommend using these default status codes, but there are situations where you might need to set a custom HTTP status code, such as when porting an existing API that clients depend on for specific status codes.

To set a custom HTTP status code, include an `HttpStatus` field in your response interface:

```ts
import { api, HttpStatus } from "encore.dev/api";

interface Response {
  msg: string;
  status: HttpStatus;
}

export const endpoint = api(
  { expose: true, method: "GET", path: "/path" },
  async (): Promise<Response> => {
    return { msg: "Hello", status: HttpStatus.Created };
  }
);
```

The `HttpStatus` enum includes all standard HTTP status codes like `HttpStatus.OK`, `HttpStatus.Created`, `HttpStatus.BadRequest`, etc.

## Raw endpoints

In case you need to operate at a lower abstraction level, Encore supports defining raw endpoints that let you access the underlying HTTP request. This is often useful for things like accepting webhooks.
Learn more in the [raw endpoints guide](/docs/ts/primitives/raw-endpoints).

## Sensitive data

When handling sensitive information like API keys, passwords, or personally identifiable information (PII), you may want to prevent these details from appearing in traces. Encore provides the `sensitive` option for API endpoints for this purpose.

To mark an endpoint as sensitive, add `sensitive: true` to the API options:

```typescript
export const processPayment = api(
  { expose: true, method: "POST", path: "/payments", sensitive: true },
  async (params: PaymentParams): Promise<PaymentResponse> => {
    return { /* ... */ };
  }
);
```

When `sensitive: true` is set, Encore automatically redacts all request and response payloads and excludes HTTP headers from traces.
